package com.luoyx.hauyne.uaa.authentication.captcha;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.luoyx.hauyne.uaa.dto.CachedCaptchaDTO;
import com.luoyx.hauyne.uaa.authentication.CaptchaAuthenticationToken;
import com.luoyx.hauyne.framework.utils.rsa.RSAUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.net.URLDecoder;

import static com.luoyx.hauyne.uaa.controller.CaptchaController.REDIS_KEY_IMAGE_CODE;

/**
 * 自定义Provider
 */
@Slf4j
@RequiredArgsConstructor
public class CaptchaAuthenticationProvider implements AuthenticationProvider {

    private final UserDetailsService myUserDetailsService;
    private final PasswordEncoder passwordEncoder;
    private final RedisTemplate redisTemplate;

    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    /**
     * 是否检查用户输入的验证码【true=是，false=否；缺省值为true】
     * 设置这个开关，主要是为了在开发环境下跳过验证码的检查，提高测试效率。测试环境视情况而定。生产环境不要配置该项
     */
    @Value("${enable.captcha.check:true}")
    private boolean enableCaptchaCheck;

    @Override
    public Authentication authenticate(Authentication authentication) {
        CaptchaAuthenticationToken authenticationToken = (CaptchaAuthenticationToken) authentication;
        validate(authenticationToken);
        String username = (String) authenticationToken.getPrincipal();
        String password = (String) authenticationToken.getCredentials();

        UserDetails user = myUserDetailsService.loadUserByUsername(username);
        if (user == null) {
            log.warn("账号不存在");
            throw new InternalAuthenticationServiceException("账号不存在");
        }

        if (!user.isEnabled()) {
            throw new DisabledException("你已被禁用");
        }

        if (!passwordEncoder.matches(password, user.getPassword())) {
            log.warn("密码错误");
            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "密码错误"));
        }
        CaptchaAuthenticationToken authenticationResult = new CaptchaAuthenticationToken(user, password, user.getAuthorities());
        authenticationResult.setDetails(authenticationToken.getDetails());

        return authenticationResult;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return CaptchaAuthenticationToken.class.isAssignableFrom(authentication);
    }

    private void validate(CaptchaAuthenticationToken authenticationToken) throws BadCredentialsException {
        log.info("enableCaptchaCheck => {}", enableCaptchaCheck);
        String requestImgCode = authenticationToken.getCaptcha();
        String captchaKey = authenticationToken.getCaptchaKey();

        if (StringUtils.isBlank(requestImgCode)) {
            log.warn("验证码不能为空");
//            throw new BadCredentialsException(JsonUtil.toString(APIError.invalidParam("验证码不能为空")));
            throw new BadCredentialsException("验证码不能为空");
        }
        Object redisImgCodeObj = redisTemplate.opsForValue().get(REDIS_KEY_IMAGE_CODE + captchaKey);
        if (null == redisImgCodeObj) {
            log.warn("验证码已过期");
//            throw new BadCredentialsException(JsonUtil.toString(APIError.invalidParam("验证码已过期")));
            throw new BadCredentialsException("验证码已过期");
        }
        CachedCaptchaDTO cachedCaptchaDTO = null;
        try {
            cachedCaptchaDTO = new ObjectMapper().readValue(redisImgCodeObj.toString(), CachedCaptchaDTO.class);

            //删除验证码
            redisTemplate.delete(REDIS_KEY_IMAGE_CODE + captchaKey);
            if (!cachedCaptchaDTO.getActualImgCode().equalsIgnoreCase(requestImgCode) && enableCaptchaCheck) {
                log.warn("验证码不正确");
                throw new BadCredentialsException("验证码不正确");
            }
            String base64Password = (String) authenticationToken.getCredentials();

            //URL decode(前端发过来的是RSA公钥加密后的base64格式密文，urlencode发送到服务器，斜杠/、加号+会被urlencode)
            String rsaEncryptPassword = URLDecoder.decode(base64Password, "utf-8");
            String privateKey = cachedCaptchaDTO.getRsaPrivateKey();
            // RSA 私钥解密
            String password = RSAUtil.decryptByPrivateKey(rsaEncryptPassword, privateKey);
            //设置解密后的密码【此时已是明文】
            authenticationToken.setCredentials(password);
        } catch (Exception e) {
            throw new BadCredentialsException(e.getMessage());
        }
    }
}
